/**
* file name  : it/ciano/jenoware/MarkerFreq.java
* authors    : Luciano Xumerle
* created    : dom 12 set 2004 10:31:50 CEST
*
* Copyright (c) 2004-2005 Luciano Xumerle. All rights reserved.
*
* This file is part of gephinfo.
*
* gephinfo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*
*/

package it.ciano.jenoware;

import java.util.*;
import it.ciano.util.CCounter;
import it.ciano.util.CString;

/**
 * The class manage information about a single marker stored into a Pedigree.
 * There is information about allele freq, genotype freq and so on.
 *
 * @author Luciano Xumerle
 * @version 0.0.1
 */
public class MarkerFreq
{
    // GLOBAL PRIVATE VARS
    private String _name;
    // key is allele name and value is allele count
    private CCounter _unrelated_alleles;
    // key is allele name and value is allele count
    private CCounter _related_alleles;
    // key is concat genotype name and value is count
    private CCounter _unrelated_genotypes;
    // key is concat genotype name and value is count
    private CCounter _related_genotypes;
    // given alleles CCounter
    private CCounter _given_alleles;
    // given genotypes CCounter
    private CCounter _given_genotypes;
    // total unrelated Sbj
    private int _unrelated;
    // total Sbj
    private int _total;

    private Allele[] _alleleList;
    private Genotype[] _genotypeList;

    /**
     * Class Constructor.
     *
     * @param ped a Pedigree object
     * @param marker a marked stored into the Pedigree object
     */
    public MarkerFreq ( Pedigree ped, String marker )
    {
        this ( ped, marker, null, null );
    }

    /**
     * Class Constructor.
     *
     * @param marker a marked stored into the Pedigree object
     * @param alleleFreqs alleles counter.
     * @param genotypeFreqs genotypes counter.
     */
    public MarkerFreq (  String marker, CCounter alleleFreqs, CCounter genotypeFreqs )
    {
        this ( null, marker, alleleFreqs, genotypeFreqs );
    }


    /**
     * Class Constructor.
     *
     * @param marker a marked stored into the Pedigree object
     * @param alleleFreqs alleles counter.
     */
    public MarkerFreq (  String marker, CCounter alleleFreqs )
    {
        this ( null, marker, alleleFreqs, null );
    }


    /**
     * Class Constructor.
     *
     * @param ped a Pedigree object
     * @param marker a marked stored into the Pedigree object
     * @param alleleFreqs alleles counter.
     * @param genotypeFreqs genotypes counter.
     */
    public MarkerFreq ( Pedigree ped, String marker, CCounter alleleFreqs, CCounter genotypeFreqs )
    {
        // initializing properties
        _name = marker;
        HashSet observed_allele = new HashSet();
        _unrelated_alleles = new CCounter();
        _related_alleles = new CCounter();
        _related_genotypes = new CCounter();
        _unrelated_genotypes = new CCounter();

        if ( genotypeFreqs==null && alleleFreqs!=null )
            genotypeFreqs=getGenotypesFreq ( marker, alleleFreqs );

        // process the pedigree object
        if ( ped==null && alleleFreqs!=null && genotypeFreqs!=null )
        {
            _total=genotypeFreqs.getTotal();
            _unrelated=genotypeFreqs.getTotal();
            _unrelated_alleles=alleleFreqs;
            _related_alleles=alleleFreqs;
            _related_genotypes=genotypeFreqs;
            _unrelated_genotypes=genotypeFreqs;
            String[] aa=alleleFreqs.getKeys();
            for ( int i=0;i<aa.length;i++ )
                observed_allele.add ( aa[i] );
        }
        else
        {
            Sbj unrelated[] = ped.getUnrelatedSbj();
            Sbj related[] = ped.getListSbj();
            _unrelated = unrelated.length;
            _total = related.length;
            // parse unrelated id
            for ( int i = 0; i < unrelated.length; i++ )
            {
                Genotype tt = unrelated[ i ].getGenotype ( marker );
                if ( tt != null && tt.isValid() )
                {
                    observed_allele.add ( tt.getAll1() );
                    observed_allele.add ( tt.getAll2() );
                    // update the CCounter objects
                    _unrelated_genotypes.inc ( tt.getConcatAlleles() );
                    _unrelated_alleles.inc ( tt.getAll1() );
                    _unrelated_alleles.inc ( tt.getAll2() );
                }
            }
            // parse related id
            for ( int i = 0; i < related.length; i++ )
            {
                Genotype tt = related[ i ].getGenotype ( marker );
                if ( tt != null && tt.isValid() )
                {
                    observed_allele.add ( tt.getAll1() );
                    observed_allele.add ( tt.getAll2() );
                    // update the CCounter objects
                    _related_genotypes.inc ( tt.getConcatAlleles() );
                    _related_alleles.inc ( tt.getAll1() );
                    _related_alleles.inc ( tt.getAll2() );
                }
            }
        }

        // set the fixed frequencies counter given by the user
        if ( alleleFreqs==null && genotypeFreqs==null && ped!=null )
        {
            _given_alleles=_unrelated_alleles;
            _given_genotypes=_unrelated_genotypes;
        }
        else
        {
            _given_alleles=alleleFreqs;
            _given_genotypes=genotypeFreqs;
        }

        // update the _alleleList and _genotypeList private arrays
        setAlleleAndGenotypeList ( observed_allele );
    }

    /**
     * Returns the marker name.
     *
     * @return the marker name.
     */
    public String getMarker()
    {
        return _name;
    }

    /**
     * Returns alleles counts in the set of the unrelated subjects.
     *
     * @return alleles counts in the set of the unrelated subjects.
     */
    final public CCounter getUnrelatedAllelesCounter()
    {
        return _unrelated_alleles;
    }

    /**
     * Returns alleles counts in the set of the related subjects.
     *
     * @return alleles counts in the set of the related subjects.
     */
    final public CCounter getRelatedAllelesCounter()
    {
        return _related_alleles;
    }

    /**
     * Returns alleles counts given by user.
     *
     * @return alleles counts given by user.
     */
    final public CCounter getUserAllelesCounter()
    {
        return _given_alleles;
    }

    /**
     * Returns genotypes counts in the set of the unrelated subjects.
     *
     * @return genotypes counts in the set of the unrelated subjects.
     */
    final public CCounter getUnrelatedGenotypesCounter()
    {
        return _unrelated_genotypes;
    }

    /**
     * Returns genotypes counts in the set of the related subjects.
     *
     * @return genotypes counts in the set of the related subjects.
     */
    final public CCounter getRelatedGenotypesCounter()
    {
        return _related_genotypes;
    }

    /**
     * Returns genotypes counts given by user.
     *
     * @return genotypes counts given by user.
     */
    final public CCounter getUserGenotypesCounter()
    {
        return _given_genotypes;
    }

    /**
     * Returns the ordered list of observed Allele objects.
     *
     * @return the ordered list.
     */
    public Allele[] getOrderedAlleleList()
    {
        return _alleleList;
    }

    /**
     * Returns the ordered list of Genotype objects.
     *
     * @return the ordered list.
     */
    public Genotype[] getOrderedGenotypeList()
    {
        return _genotypeList;
    }

    /**
     * Returns the total number of unrelated subjects.
     *
     * @return the value.
     */
    public int countUnrelatedID()
    {
        return _unrelated;
    }

    /**
     * Returns the total number of related subjects.
     *
     * @return the value.
     */
    public int countRelatedID()
    {
        return _total;
    }

    /**
     * Returns the number of genotyped subjects in the set of unrelated subjects.
     *
     * @return the value.
     */
    public int countGenotypedUnrelatedID()
    {
        return _unrelated_genotypes.getTotal();
    }

    /**
     * Returns the number of genotyped subjects in the set of related subjects.
     *
     * @return the value.
     */
    public int countGenotypedRelatedID()
    {
        return _related_genotypes.getTotal();
    }

    /**
     * Returns the number of genotyped subjects using user's CCounter objects.
     *
     * @return the value.
     */
    public int countUserRelatedID()
    {
        return _given_genotypes.getTotal();
    }

    /**
     * Returns the total occurrences of the allele in the set of unrelated subjects.
     *
     * @param allele the allele.
     * @return the value.
     */
    public int getUnrelatedAlleleCount ( String allele )
    {
        return _unrelated_alleles.getOccurrences ( allele );
    }

    /**
     * Returns the total occurrences of the allele in the set of related subjects.
     *
     * @param allele the allele.
     * @return the value.
     */
    public int getRelatedAlleleCount ( String allele )
    {
        return _related_alleles.getOccurrences ( allele );
    }

    /**
     * Returns the total occurrences of the allele using user's CCounter objects.
     *
     * @param allele the allele.
     * @return the value.
     */
    public int getUserAlleleCount ( String allele )
    {
        return _given_alleles.getOccurrences ( allele );
    }

    /**
     * Returns the total occurrences of the genotype in the set of unrelated subjects.
     *
     * @param geno the Genotype.getConcatAlleles() String.
     * @return the value.
     */
    public int getUnrelatedGenotypeCount ( String geno )
    {
        return _unrelated_genotypes.getOccurrences ( geno );
    }

    /**
     * Returns the total occurrences of the genotype in the set of related subjects.
     *
     * @param geno the Genotype.getConcatAlleles() String.
     * @return the value.
     */
    public int getRelatedGenotypeCount ( String geno )
    {
        return _related_genotypes.getOccurrences ( geno );
    }

    /**
     * Returns the total occurrences of the genotype using user's CCounter objects.
     *
     * @param geno the Genotype.getConcatAlleles() String.
     * @return the value.
     */
    public int getUserGenotypeCount ( String geno )
    {
        return _given_genotypes.getOccurrences ( geno );
    }

    /**
     * Returns the frequence of the allele in the set of unrelated subjects.
     *
     * @param allele the allele name.
     * @return the value.
     */
    public float getUnrelatedAlleleFreq ( String allele )
    {
        return _unrelated_alleles.getFrequencies ( allele );
    }

    /**
     * Returns the frequence of the allele in the set of related subjects.
     *
     * @param allele the allele name.
     * @return the value.
     */
    public float getRelatedAlleleFreq ( String allele )
    {
        return _related_alleles.getFrequencies ( allele );
    }

    /**
     * Returns the frequence of the allele using user's CCounter objects.
     *
     * @param allele the allele name.
     * @return the value.
     */
    public float getUserAlleleFreq ( String allele )
    {
        return _given_alleles.getFrequencies ( allele );
    }

    /**
     * Returns the frequence of the genotype in the set of unrelated subjects.
     *
     * @param geno the Genotype.getConcatAlleles() String.
     * @return the value.
     */
    public float getUnrelatedGenotypeFreq ( String geno )
    {
        return _unrelated_genotypes.getFrequencies ( geno );
    }

    /**
     * Returns the frequence of the genotype in the set of related subjects.
     *
     * @param geno the Genotype.getConcatAlleles() String.
     * @return frequence.
     */
    public float getRelatedGenotypeFreq ( String geno )
    {
        return _related_genotypes.getFrequencies ( geno );
    }

    /**
     * Returns the frequence of the genotype using user's CCounter objects.
     *
     * @param geno the Genotype.getConcatAlleles() String.
     * @return frequence.
     */
    public float getUserGenotypeFreq ( String geno )
    {
        return _given_genotypes.getFrequencies ( geno );
    }

    /**
     * Returns the ordered list of alleles' absolute frequencies
     * (used to compute the HWE test).
     *
     * @return the list of values.
     */
    public int[] getObservedAllele()
    {
        if ( _alleleList.length > 0 )
        {
            int ff[] = new int[ _alleleList.length ];
            for ( int i = 0; i < _alleleList.length; i++ )
                ff[ i ] = getUnrelatedAlleleCount ( _alleleList[ i ].toString() );
            return ff;
        }
        return new int[ 0 ];
    }

    /**
     * Returns the list of genotypes' absolute frequencies
     * (used to compute the HWE test).
     *
     * @return the list of values.
     */
    public int[] getObservedGenotype()
    {
        if ( _alleleList.length > 0 )
        {
            int kk = 0;
            int res[] = new int[ HWE.getGenotypeClassesFromAlleleClasses ( _alleleList.length ) ];
            for ( int i = 0; i < _alleleList.length; i++ )
                for ( int j = 0; j <= i;j++ )
                {
                    Genotype tt = new Genotype ( getMarker(),
                            _alleleList[ i ], _alleleList[ j ] );
                    res[ kk++ ] = getUnrelatedGenotypeCount ( tt.getConcatAlleles() );
                }
            return res;
        }
        return new int[ 0 ];
    }

    /**
     * Returns the result for the test of Hardy-Weinberg equilibrium.
     *
     * @param stepNum number of simulation (if 0 then no simulation).
     * @param useNew use the new random method in simulation.
     * @return Chi-Square, df and 2p.
     */
    public String getHWE ( int stepNum, boolean useNew )
    {
        return new HWE ( getObservedAllele(), getObservedGenotype(), stepNum, useNew ).toString();
    }

    /**
     * Returns the ordered list of Allele values.
     *
     * @return the ordered list.
     */
    public String[] getStringAlleles()
    {
        String res[] = null;
        if ( _alleleList.length > 0 )
        {
            res = new String[ _alleleList.length ];
            for ( int i = 0; i < _alleleList.length; i++ )
                res[ i ] = _alleleList[ i ].toString();
        }
        return res;
    }

    /**
     * Returns the position of the Allele in the ordered Allele list.
     *
     * @param allele the allele value.
     * @return the position.
     */
    public int getAllelePosition ( Allele allele )
    {
        for ( int i = 0; i < _alleleList.length; i++ )
            if ( allele.equals ( _alleleList[ i ] ) )
                return 1 + i;
        return 0;
    }

    /**
     * Returns the position of the Genotype in the ordered Genotype list.
     *
     * @param geno the Genotype object.
     * @return the position.
     */
    public int getGenotypePosition ( Genotype geno )
    {
        for ( int i = 0; i < _genotypeList.length; i++ )
            if ( geno.equals ( _genotypeList[ i ] ) )
                return 1 + i;
        return 0;
    }

    /**
     * Overriding toString method.
     *
     * @return the descriptive statistics of the marker.
     */
    public String toString()
    {
        String white = "================================================================================\n";
        String locus = "Locus: " + getMarker();
        String freqAll = "Alleles frequencies\n" + "^^^^^^^^^^^^^^^^^^^\n";
        String freqGeno = "Genotypes frequencies\n" + "^^^^^^^^^^^^^^^^^^^^^\n";
        String totalSet = " (TOTAL SET)";
        String unrelatedSet = " (UNRELATED SET)";

        String observedAllele="Observed Labels: " + CString.join ( getStringAlleles(), " - " );

        String total = white + locus + totalSet + "\n" + white + "\n" + observedAllele + "\n\n" + freqAll + "\n";
        String unrelated = white + locus + unrelatedSet + "\n" + white + "\n" + freqAll + "\n";
        // parsing allele
        for ( int i = 0; i < _alleleList.length; i++ )
        {
            String allele = _alleleList[ i ].getValue();
            total += "Allele: " + allele
                    + " Observed: " + getRelatedAlleleCount ( allele )
                    + " Freq.: " + getRelatedAlleleFreq ( allele ) + "\n";
            unrelated += "Allele: " + allele
                    + " Observed: " + getUnrelatedAlleleCount ( allele )
                    + " Freq.: " + getUnrelatedAlleleFreq ( allele ) + "\n";
        }
        total += "Genotyped: " + _related_alleles.getTotal() + "\n";
        total += "NOT Genotyped: " + ( _total * 2 - _related_alleles.getTotal() ) + "\n";
        total += "Total Set: " + ( _total * 2 ) + "\n\n";
        unrelated += "Genotyped: " + _unrelated_alleles.getTotal() + "\n";
        unrelated += "NOT Genotyped: " + ( _unrelated * 2 - _unrelated_alleles.getTotal() ) + "\n";
        unrelated += "Total Set: " + ( _unrelated * 2 ) + "\n\n";
        // parsing genotype
        unrelated += freqGeno + "\n";
        total += freqGeno + "\n";
        for ( int i = 0; i < _genotypeList.length; i++ )
        {
            String geno = _genotypeList[ i ].getConcatAlleles();
            total += "Genotype: " + geno
                    + " Observed: " + getRelatedGenotypeCount ( geno )
                    + " Freq.: " + getRelatedGenotypeFreq ( geno ) + "\n";
            unrelated += "Genotype: " + geno
                    + " Observed: " + getUnrelatedGenotypeCount ( geno )
                    + " Freq.: " + getUnrelatedGenotypeFreq ( geno ) + "\n";
        }
        total += "Genotyped: " + _related_genotypes.getTotal() + "\n";
        total += "NOT Genotyped: " + ( _total - _related_genotypes.getTotal() ) + "\n";
        total += "Total Set: " + _total + "\n\n";
        unrelated += "Genotyped: " + _unrelated_genotypes.getTotal() + "\n";
        unrelated += "NOT Genotyped: " + ( _unrelated - _unrelated_genotypes.getTotal() ) + "\n";
        unrelated += "Total Set: " + _unrelated + "\n";
        return total + unrelated + "\n";
    }

    /**
     * Set the private array _alleleList and _genotypeList.
     *
     * @param all the set of observed allele.
     */
    private void setAlleleAndGenotypeList ( HashSet all )
    {
        // set allele order
        ArrayList pp = new ArrayList();
        for ( Iterator it = all.iterator(); it.hasNext(); )
        {
            Allele tmp = new Allele ( ( String ) it.next() );
            if ( tmp.isValid() )
                pp.add ( tmp );
        }
        if ( pp.size() > 0 )
        {
            _alleleList = new Allele[ pp.size() ];
            for ( int i = 0; i < pp.size(); i++ )
                _alleleList[ i ] = ( Allele ) pp.get ( i );
        }
        else
        {
            _alleleList = new Allele[ pp.size() ];
            _genotypeList = new Genotype[ pp.size() ];
            return ;
        }
        Arrays.sort ( _alleleList );
        // set Genotype order
        pp = new ArrayList();
        for ( int i = 0; i < _alleleList.length; i++ )
            for ( int j = i; j < _alleleList.length; j++ )
                pp.add ( new Genotype ( _name, _alleleList[ i ], _alleleList[ j ] ) );
        _genotypeList = new Genotype[ pp.size() ];
        for ( int i = 0; i < pp.size();i++ )
            _genotypeList[ i ] = ( Genotype ) pp.get ( i );
    }

    /**
     * Returns the homozygosity using the given frequencies.
     *
     * @param values the array containing the frequencies list.
     * @return the homozygosity value.
     */
    final public static double getHomozygosity ( double[] values )
    {
        double sum=0;
        for ( int i=0;i<values.length;i++ )
            sum+= ( values[i]*values[i] );
        return sum;
    }

    /**
     * Returns the homozygosity.
     *
     * @return the homozygosity value.
     */
    final public double getHomozygosity()
    {
        Allele aa[]=getOrderedAlleleList();
        double[] ff=new double[aa.length];
        for ( int a=0;a<ff.length;a++ )
            ff[a]= ( double ) getUnrelatedAlleleFreq ( aa[a].toString() );
        return getHomozygosity ( ff );
    }

    /**
     * Returns the heterozygosity using the given frequencies.
     *
     * @param values the array containing the frequencies list.
     * @return the heterozygosity value.
     */
    final public static double getHeterozygosity ( double[] values )
    {
        return 1-getHomozygosity ( values );
    }

    /**
     * Returns the heterozygosity.
     *
     * @return the heterozygosity value.
     */
    final public double getHeterozygosity ()
    {
        return 1-getHomozygosity ();
    }

    /**
     * Returns a CCounter containig genotype frequencies using the HWE rules.
     *
     * @param alleles Allele frequencies.
     * @return The genotype frequencies.
     */
    public static CCounter getGenotypesFreq ( String marker, CCounter alleles )
    {
        CCounter geno=new CCounter();
        int total=alleles.getTotal() / 2;
        String[] all=alleles.getKeys();
        int subtotal=0;
        for ( int i=0; i<all.length; i++ )
            for ( int j=i; j<all.length; j++ )
            {
                Genotype tt=new Genotype ( marker,all[i], all[j] );
                String k=tt.getConcatAlleles();
                int d=0;
                if ( i==j )
                    d= ( int ) ( ( double ) alleles.getOccurrences ( all[i] ) * ( double ) alleles.getOccurrences ( all[i] ) /alleles.getTotal() );
                else
                    d= ( int ) ( 2 * ( double ) alleles.getOccurrences ( all[i] ) * ( double ) alleles.getOccurrences ( all[j] ) /alleles.getTotal() );
                subtotal+=d;
                geno.setOccurrences ( k, d );
            }
        geno.setTotal ( subtotal );
        return geno;
    }

} // end class

